home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 8614 / 8614.xpi / modules / application / extractors / DefaultTextExtractor.jsm < prev   
Text File  |  2010-02-10  |  28KB  |  1,058 lines

  1. // DO NOT import this into the global namespace, but instead
  2. // import it into your own namespace wrapper
  3.  
  4. var EXPORTED_SYMBOLS = ["DefaultTextExtractor"];
  5.  
  6. Components.utils.import("resource://glydo/utils/prototype_xul_1_6_0_3_modified.jsm");
  7. Components.utils.import("resource://glydo/utils/Utils.jsm");
  8. Components.utils.import("resource://glydo/utils/Prefs.jsm");
  9.  
  10. DefaultTextExtractor = {};
  11.  
  12. DefaultTextExtractor.TAG_TYPES = {
  13.         "a": "anchor",
  14.         "applet": "skip",
  15.         "base": "skip",
  16.         "blockquote": "par",
  17.         "br": "line-break",
  18.         "body": "div",
  19.         "caption": "par",
  20.         "code": "skip",
  21.         "dd": "line-break",
  22.         "del": "skip",
  23.         "dir": "par",
  24.         "div": "div",
  25.         "dt": "par-start",
  26.         "dl": "par",
  27.         "embed": "skip",
  28.         "frame": "skip",
  29.         "h1": "header",
  30.         "h2": "header",
  31.         "h3": "header",
  32.         "h4": "header",
  33.         "h5": "header",
  34.         "h6": "header",
  35.         "hr": "line-break",
  36.         "iframe": "skip",
  37.         "img": "skip",
  38.         "li": "par",
  39.         "link": "skip",
  40.         "map": "skip",
  41.         "menu": "par",
  42.         "noframes": "skip",
  43.         "noscript": "skip",
  44.         "object": "skip",
  45.         "ol": "par",
  46.         "p": "par",
  47.         "pre": "par",
  48.         "samp": "skip",
  49.         "script": "skip",
  50.         "select": "skip",
  51.         "style": "skip",
  52.         "table": "div",
  53.         "head": "skip",
  54.         "td": "par",
  55.         "th": "par",
  56.         "var": "skip"
  57. };
  58.  
  59. DefaultTextExtractor.STYLE_PROPERTIES = [
  60.                                                     
  61.                                                     ["fontWeight","font-weight"],
  62.                                                     ["fontSize","font-size"]
  63.                                                 ];
  64.  
  65.  
  66.  
  67. DefaultTextExtractor.Container = Prototype.Class.create({
  68.     initialize: function(depth,separator) {
  69.         this.objects = [];
  70.         this.headerCandidates = [];
  71.         this.depth = depth;
  72.         this.score = null;
  73.         this.nTextWordsCount = null;
  74.         this.nLinkWordsCount = null;
  75.         this.nLocalTextWordsCount = null;
  76.         this.nLocalLinkWordsCount = null;
  77.         if (separator === undefined) {
  78.             this.separator = "";
  79.         } else {
  80.             this.separator = separator;
  81.         }
  82.     },
  83.     
  84.     isLocked: function() {
  85.         return this.nTextWordsCount !== null || 
  86.             this.nLinkWordsCount !== null ||
  87.             this.score !== null;
  88.     },
  89.     
  90.     getTextWordsCount: function() {
  91.         if (this.nTextWordsCount === null) {
  92.             var n = 0;
  93.             this.objects.forEach(function(o) {
  94.                 n += o.getTextWordsCount();
  95.             },this);
  96.             this.nTextWordsCount = n;
  97.         }
  98.         return this.nTextWordsCount;
  99.     },
  100.     
  101.     getLocalTextWordsCount: function() {
  102.         if (this.nLocalTextWordsCount === null) {
  103.             var n = 0;
  104.             this.objects.forEach(function(o) {
  105.                 if (o instanceof DefaultTextExtractor.Paragraph) {
  106.                     n += o.getTextWordsCount();
  107.                 }
  108.             },this);
  109.             this.nLocalTextWordsCount = n;
  110.         }
  111.         return this.nLocalTextWordsCount;
  112.     },
  113.  
  114.     getLinkWordsCount: function() {
  115.         if (this.nLinkWordsCount === null) {
  116.             var n = 0;
  117.             this.objects.forEach(function(o) {
  118.                 n += o.getLinkWordsCount();
  119.             },this);
  120.             this.nLinkWordsCount = n;
  121.         }
  122.         return this.nLinkWordsCount;
  123.     },
  124.  
  125.     getLocalLinkWordsCount: function() {
  126.         if (this.nLocalLinkWordsCount === null) {
  127.             var n = 0;
  128.             this.objects.forEach(function(o) {
  129.                 if (o instanceof DefaultTextExtractor.Paragraph) {
  130.                     n += o.getLinkWordsCount();
  131.                 }
  132.             },this);
  133.             this.nLocalLinkWordsCount = n;
  134.         }
  135.         return this.nLocalLinkWordsCount;
  136.     },
  137.     
  138.     isEmpty: function() {
  139.         return this.getTextWordsCount() == 0;
  140.     },
  141.     
  142.     add: function(item) {
  143.         if (this.isLocked()) {
  144.             throw "Cannot modify container once scores have been calculated";
  145.         }
  146.         this.objects.push(item);
  147.     },
  148.     
  149.     addHeaderCandidate: function(item) {
  150.         this.headerCandidates.push(item);
  151.     },
  152.     
  153.     shouldPrune: function() {
  154.         return this.isEmpty();
  155.     },
  156.     
  157.     toXml: function(doc,params,name) {
  158.         if (!params || !params.dontPrune) {
  159.             if (this.shouldPrune()) {
  160.                 return null;
  161.             }
  162.         }
  163.         if (this.isEmpty()) {
  164.             return null;
  165.         }
  166.         if (name === undefined) {
  167.             name = "container";
  168.         }
  169.         var elem = doc.createElement(name);
  170.         if (this.score != undefined) {
  171.             elem.setAttribute("score", this.score);
  172.         }
  173.         for (var oi = 0; oi < this.objects.length; ++oi) {
  174.             var xml = this.objects[oi].toXml(doc,params);
  175.             if (xml !== null) {
  176.                 elem.appendChild(xml);
  177.             }
  178.         }
  179.         return elem;
  180.     },
  181.     
  182.     toText: function() {
  183.         var res = [];
  184.         for (var oi = 0; oi < this.objects.length; ++oi) {
  185.             res.push(this.objects[oi].toText());
  186.         }
  187.         return res.join(this.separator);
  188.     },
  189.  
  190.     containsAnyOf: function(searchStrings) {
  191.         return Utils.containsAnyOf(this.toText(),searchStrings);
  192.     },
  193.  
  194.     calcScore: function() {
  195.         this.score = 0;
  196.         this.objects.forEach(Prototype.F.bind(
  197.                 function(o) {
  198.                     if (o.calcScore !== undefined) {
  199.                         o.calcScore();
  200.                         if (!o.shouldPrune()) {
  201.                             this.score += o.score;
  202.                         }
  203.                     }
  204.                 },this)
  205.             );
  206.     },
  207.  
  208.     collect: function(predicate,resultList) {
  209.         if (resultList === undefined) {
  210.             resultList = [];
  211.         }
  212.         for (var oi = 0; oi < this.objects.length; ++oi) {
  213.             var o = this.objects[oi];
  214.             if (predicate(o)) {
  215.                 resultList.push(o);
  216.             } else if (o.collect !== undefined) {
  217.                 o.collect(predicate,resultList);
  218.             }
  219.         }
  220.         return resultList;
  221.     },
  222.  
  223.     collectHeaderCandidates: function(resultList) {
  224.         if (resultList === undefined) {
  225.             resultList = [];
  226.         }
  227.         for (var i = 0; i < this.headerCandidates.length; ++i) {
  228.             resultList.push(this.headerCandidates[i]);
  229.         }
  230.         for (var oi = 0; oi < this.objects.length; ++oi) {
  231.             var o = this.objects[oi];
  232.             if (o instanceof DefaultTextExtractor.Container) {
  233.                 o.collectHeaderCandidates(resultList);
  234.             }
  235.         }
  236.         return resultList;
  237.     },
  238.  
  239.     collectLocal: function(predicate,resultList) {
  240.         if (resultList === undefined) {
  241.             resultList = [];
  242.         }
  243.         for (var oi = 0; oi < this.objects.length; ++oi) {
  244.             var o = this.objects[oi];
  245.             if (predicate(o)) {
  246.                 resultList.push(o);
  247.             }
  248.         }
  249.         return resultList;
  250.     }
  251. });
  252.  
  253. DefaultTextExtractor.Text = Prototype.Class.create({
  254.     initialize: function(container,parentElement,text,isLink,remLeadingSpaces) {
  255.         this.container = container;
  256.         this.text = "";
  257.         this.isLink = isLink;
  258.         this.remLeadingSpaces = remLeadingSpaces;
  259.         this.storeStyle(parentElement);
  260.         this.addText(text);
  261.     },
  262.  
  263.     setRemLeadingSpaces: function(remLeadingSpaces) {
  264.         this.remLeadingSpaces = remLeadingSpaces;
  265.     },
  266.     
  267.     storeStyle: function(element) {
  268.         var style = {};
  269.         DefaultTextExtractor.STYLE_PROPERTIES.forEach(function(p) {
  270.             var s = Prototype.E.getStyle(element,p[1]);
  271.             if (s !== null) {
  272.                 style[p[0]] = s;
  273.             }
  274.         },this);
  275.         this.style = style;
  276.     },
  277.     
  278.     isCompatibleWith: function(otherText) {
  279.         if (otherText.isLink !== this.isLink) {
  280.             return false;
  281.         }
  282.         return this.stylesCompatible(this.style,otherText.style);
  283.     },
  284.     
  285.     addText: function(text) {
  286.         this.text += text;
  287.         var words = this.text.split(/\s+/);
  288.         var n = 0;
  289.         for (var i = 0; i < words.length; ++i) {
  290.             if (words[i].length != 0) {
  291.                 n++;
  292.             }
  293.         }
  294.         this.nWords = n;
  295.         this.strippedText = this.text.replace(/\s+/g," ");
  296.         if (this.remLeadingSpaces) {
  297.             this.lstrip();
  298.         }
  299.         var fontSize = Utils.getPixelsFromStyleSizeStr(this.style["fontSize"]);
  300.         if (fontSize >= 16 && this.style["fontWeight"] == "bold") {
  301.             this.container.addHeaderCandidate(this);
  302.         }
  303.     },
  304.     
  305.     rstrip: function() {
  306.         var l = this.strippedText.length;
  307.         if ((l > 0) && (this.strippedText.charAt(l-1) === ' ')) {
  308.             this.strippedText = this.strippedText.substring(0,l-1);
  309.         }
  310.     },
  311.     
  312.     lstrip: function() {
  313.         var l = this.strippedText.length;
  314.         if ((l > 0) && (this.strippedText.charAt(0) === ' ')) {
  315.             this.strippedText = this.strippedText.substring(1);
  316.         }
  317.     },
  318.  
  319.     toText: function() {
  320.         return this.strippedText;
  321.     },
  322.  
  323.     // TODO: What about locking of text objects?
  324.     getTextWordsCount: function() {
  325.         return this.nWords;
  326.     },
  327.     
  328.     getLinkWordsCount: function() {
  329.         return this.isLink ? this.nWords : 0;
  330.     },
  331.     
  332.     shouldPrune: function() {
  333.         return false;
  334.     },
  335.     
  336.     toXml: function(doc,params) {
  337.         var text = doc.createTextNode(this.toText());
  338.         var elem = null;
  339.         if (this.isLink) {
  340.             elem = doc.createElement("a");
  341.         } else if (params && params.styleInfo) {
  342.             elem = doc.createElement("span");
  343.         }
  344.         if (elem) {
  345.             elem.appendChild(text);
  346.             if (params && params.styleInfo) {
  347.                 params.styles = params.styles || [];
  348.                 var foundStyle = Prototype.A.find(params.styles,function(prevStyle) {
  349.                     return this.stylesCompatible(prevStyle.values,this.style);
  350.                 },this);
  351.                 if (!foundStyle) {
  352.                     var n = params.styles.length;
  353.                     foundStyle = {
  354.                         id: n,
  355.                         values: this.style
  356.                     };
  357.                     params.styles.push(foundStyle);
  358.                 }
  359.                 elem.setAttribute("style",foundStyle.id);
  360.             }
  361.             return elem;
  362.         }
  363.         return text;
  364.     },
  365.  
  366.     stylesCompatible: function(styleA,styleB) {
  367.         var p;
  368.         for (p in styleA) {
  369.             if (styleA[p] !== styleB[p]) {
  370.                 return false;
  371.             }
  372.         }
  373.         for (p in styleB) {
  374.             if (styleA[p] !== styleB[p]) {
  375.                 return false;
  376.             }
  377.         }
  378.         return true;
  379.     }
  380. });
  381.  
  382. DefaultTextExtractor.Paragraph = Prototype.Class.create(DefaultTextExtractor.Container,{
  383.     
  384.     initialize: function($super,containingBlock,depth) {
  385.         $super(depth);
  386.         this.containingBlock = containingBlock;
  387.         this.nLinks = 0;
  388.         this.inLink = false;
  389.     },
  390.     
  391.     addText: function(parentElement,text) {
  392.         if (this.isLocked()) {
  393.             throw "Cannot modify paragraph once scores have been calculated";
  394.         }
  395.         if (text !== undefined) {
  396.             var newText = new DefaultTextExtractor.Text(this, parentElement, text, this.inLink, false);
  397.             var nObjects = this.objects.length;
  398.             var prevSpace = true;
  399.             var remLeadingSpaces = true;
  400.             if (nObjects > 0) {
  401.                 var lastText = this.objects[nObjects-1];
  402.                 if ((lastText instanceof DefaultTextExtractor.Text) &&
  403.                         lastText.isCompatibleWith(newText)) {
  404.                     lastText.addText(text);
  405.                     return;
  406.                 }
  407.                 var t = lastText.toText();
  408.                 if (t.length == 0 || !Prototype.S.blank(t.substring(t.length-1))) { 
  409.                     prevSpace = false;
  410.                     remLeadingSpaces = false;
  411.                 }
  412.                 // If the previous text ended with a blank and the
  413.                 // current text begins with a blank, remove one of them
  414.                 if (prevSpace && Prototype.S.blank(text.charAt(0))) {
  415.                     // If the last text is a link, remove the space from that
  416.                     if (lastText.isLink) {
  417.                         lastText.rstrip();
  418.                         remLeadingSpaces = false;
  419.                     }
  420.                 }
  421.             }
  422.             newText.setRemLeadingSpaces(remLeadingSpaces);
  423.             this.add(newText);
  424.         }
  425.     },
  426.  
  427.     trimTrailingSpaces: function() {
  428.         if (this.isLocked()) {
  429.             throw "Cannot modify container once scores have been calculated";
  430.         }
  431.         var nObjects = this.objects.length;
  432.         prevSpace = true;
  433.         if (nObjects > 0) {
  434.             var last = this.objects[nObjects-1];
  435.             last.rstrip();
  436.         }
  437.     },
  438.     
  439.     close: function() {
  440.         if (this.isLocked()) {
  441.             throw "Cannot modify container once scores have been calculated";
  442.         }
  443.         this.trimTrailingSpaces();
  444.     },
  445.     
  446.     openLink: function() {
  447.         if (this.isLocked()) {
  448.             throw "Cannot modify container once scores have been calculated";
  449.         }
  450.         this.nLinks++;
  451.         this.inLink = true;
  452.     },
  453.  
  454.     closeLink: function() {
  455.         if (this.isLocked()) {
  456.             throw "Cannot modify container once scores have been calculated";
  457.         }
  458.         this.inLink = false;
  459.     },
  460.  
  461.     addLineBreak: function() {
  462.         if (this.isLocked()) {
  463.             throw "Cannot modify container once scores have been calculated";
  464.         }
  465.         this.trimTrailingSpaces();
  466.         this.add(new DefaultTextExtractor.LineBreak());
  467.     },
  468.  
  469.     toXml: function($super,doc,params,name) {
  470.         if (name === undefined) {
  471.             name = "p";
  472.         }
  473.         return $super(doc,params,name);
  474.     },
  475.  
  476.     shouldPrune: function($super) {
  477.         return $super() || this.score <= -5;
  478.     },
  479.  
  480.     calcScore: function() {
  481.         this.score = 0;
  482.         var ntw = this.getTextWordsCount(); 
  483.         var nlw = this.getLinkWordsCount(); 
  484.         if (ntw > 0) {
  485.             this.score -= 10*nlw/ntw;
  486.         }
  487.         // Paragraph contains too many separate links (from daled)
  488.         if (ntw > 4 && nlw > 2) {
  489.             this.score -= 10*nlw/ntw;
  490.         }
  491.         this.score += (ntw - nlw)/4.0;
  492.     }
  493. });
  494.  
  495. DefaultTextExtractor.LineBreak = Prototype.Class.create({
  496.     getTextWordsCount: function() {
  497.         return 0;
  498.     },
  499.         
  500.     getLinkWordsCount: function() {
  501.         return 0;
  502.     },
  503.  
  504.     toText: function() {
  505.         return "\n";
  506.     },
  507.  
  508.     toXml: function(doc,params) {
  509.         return doc.createElement("br");
  510.     },
  511.     
  512.     rstrip: function () {
  513.     },
  514.     
  515.     lstrip: function() {
  516.     }
  517.  
  518. });
  519.  
  520. DefaultTextExtractor.Header = Prototype.Class.create(DefaultTextExtractor.Paragraph,{
  521.     initialize: function($super,containingBlock,depth,level) {
  522.         $super(containingBlock,depth);
  523.         this.level = level;
  524.     },
  525.     
  526.     calcScore: function($super) {
  527.         if (this.level == 1) {
  528.             var ntw = this.getTextWordsCount(); 
  529.             var nlw = this.getLinkWordsCount(); 
  530.             this.score = 5 + (ntw+nlw)/2.0;
  531.         }    else {
  532.             $super();
  533.         }
  534.     },
  535.     
  536.     toXml: function($super,doc,params,name) {
  537.         if (name === undefined) {
  538.             name = "h";
  539.         }
  540.         var elem = $super(doc,params,name);
  541.         if (elem !== null) {
  542.             elem.setAttribute("level", this.level);
  543.         }
  544.         return elem;
  545.     }
  546. });
  547.  
  548.         
  549. DefaultTextExtractor.Block = Prototype.Class.create(DefaultTextExtractor.Container,{
  550.     initialize: function($super,depth,element,position,scrollPosition,rootDimensions) {
  551.         $super(depth,'\n\n');
  552.         this.maxScore = null;
  553.         this.rootDimensions = rootDimensions;
  554.         this.dimensions = null;
  555.         this.position = null;
  556.         this.visible = null;
  557.         this.nodes = {}
  558.         this.notInteresting = false;
  559.         if (element) {
  560.             this.dimensions = Prototype.E.getDimensions(element);
  561.             this.position = position || Prototype.E.cumulativeOffset(element);
  562.             // this is "1" because the first block holds the real width and height
  563.             if (depth == 1) {
  564.                 this.rootDimensions = this.dimensions;
  565.             }
  566.         }
  567.         this.punish = this.calcPunishment();
  568.         if (this.punish > 3) {
  569.             this.notInteresting = true;
  570.         }
  571.         this.openPar();
  572.     },
  573.     
  574.     getBestBlock: function() {
  575.         this.maxScore = 0;
  576.         var bestBlock = null;
  577.         var descBestBlock = null;
  578.         for (var iObject = 0; iObject < this.objects.length; ++iObject) {
  579.             var object = this.objects[iObject];
  580.             if (!(object instanceof DefaultTextExtractor.Block)) {
  581.                 continue;
  582.             }
  583.             descBestBlock = object.getBestBlock();
  584.             if (descBestBlock !== null && this.maxScore < descBestBlock.maxScore) {
  585.                 bestBlock = descBestBlock;
  586.                 this.maxScore = descBestBlock.maxScore;
  587.             }
  588.         }
  589.         var score = this.calcLocalScore();
  590.         if (bestBlock === null || score > this.maxScore) {
  591.             bestBlock = this;
  592.             this.maxScore = score;
  593.         }
  594.         return bestBlock;
  595.     },
  596.     
  597.     calcLocalScore: function() {
  598.         var nAllWords = this.getLocalTextWordsCount();
  599.         var nLinkWords = this.getLocalLinkWordsCount();
  600.         var nOnlyTextWords = nAllWords - nLinkWords;
  601.         var score = 0;
  602.         if (nLinkWords + nOnlyTextWords != 0) {
  603.             score = nOnlyTextWords / (nLinkWords + nOnlyTextWords) * Math.sqrt(nOnlyTextWords);
  604.         }
  605.         score = score/this.punish;
  606.         
  607.         return score;
  608.     },
  609.     
  610.     calcPunishment: function() {
  611.         var punish = 1;
  612.         // punish divs that don't start at initial top view
  613.         if (this.position.top > 800) {
  614.             var factor = (this.position.top - 800) / 100 + 1;
  615.             if (factor > 5) {
  616.                 factor = 5;
  617.             }
  618.             punish = punish * factor;
  619.         }
  620.         if (this.rootDimensions !== undefined) {
  621.             // punish divs with width < 30%
  622.             if (this.dimensions.width < this.rootDimensions.width*0.3) {
  623.                 // factor - between 1 and 2.5
  624.                 var factor = (this.rootDimensions.width*0.3 - this.dimensions.width) / this.rootDimensions.width * 5 + 1;
  625.                 if (factor > 3) {
  626.                     factor = 3
  627.                 }
  628.                 punish = punish * factor;
  629.             }
  630.         }
  631.         return punish;
  632.     },
  633.  
  634.     openBlock: function(element,elementId,position,scrollPosition) {
  635.         var b = new DefaultTextExtractor.Block(this.depth+1,element,position,scrollPosition,this.rootDimensions);
  636.         this.add(b);
  637.         this.nodes[elementId] = b;
  638.         return b;
  639.     },
  640.  
  641.     getBlockByElement: function(elementId) {
  642.         return this.nodes[elementId];
  643.     },
  644.         
  645.     addLineBreak: function() {
  646.         this.curPar.addLineBreak();
  647.     },
  648.     
  649.     addText: function(parentElement,text) {
  650.         this.curPar.addText(parentElement,text);
  651.     },
  652.     
  653.     openLink: function() {
  654.         this.curPar.openLink();
  655.     },
  656.     
  657.     closeLink: function() {
  658.         this.curPar.closeLink();
  659.     },
  660.     
  661.     getNormalParagraphs: function(paragraphDisqualifyingWords) {
  662.         return this.collectLocal(function(o) {
  663.             if ( 
  664.                 (o instanceof DefaultTextExtractor.Paragraph) && 
  665.                 !(o instanceof DefaultTextExtractor.Header)) {
  666.                 return !o.containsAnyOf(paragraphDisqualifyingWords);
  667.             }
  668.         });
  669.     },
  670.             
  671.     openHeader: function(level) {
  672.         this.curPar = new DefaultTextExtractor.Header(this, this.depth+1, level);
  673.         this.add(this.curPar);
  674.         if (level <= 2) {
  675.             this.addHeaderCandidate(this.curPar);
  676.         }
  677.     },
  678.  
  679.     openPar: function() {
  680.         this.curPar = new DefaultTextExtractor.Paragraph(this, this.depth+1);
  681.         this.add(this.curPar);
  682.     },
  683.     
  684.     closePar: function() {
  685.         this.curPar.close();
  686.         this.curPar = new DefaultTextExtractor.Paragraph(this, this.depth+1);
  687.         this.add(this.curPar);
  688.     },
  689.     
  690.     shouldPrune: function($super) {
  691.         return $super() || this.score < 3;
  692.     },
  693.  
  694.     toXml: function ($super,doc,params,name) {
  695.         if (name === undefined) {
  696.             name = "block";
  697.         }
  698.         var elem = $super(doc,params,name);
  699.         if (elem) {
  700.             if (!this.display) {
  701.                 elem.setAttribute("d","0");
  702.             }
  703.             if (this.dimensions) {
  704.                 elem.setAttribute("w",this.dimensions.width);
  705.                 elem.setAttribute("h",this.dimensions.height);
  706.             }
  707.             if (this.position) {
  708.                 elem.setAttribute("l",this.position.left);
  709.                 elem.setAttribute("t",this.position.top);
  710.             }
  711.             if (this.viewportPosition) {
  712.                 elem.setAttribute("vl",this.viewportPosition.left);
  713.                 elem.setAttribute("vt",this.viewportPosition.top);
  714.             }
  715.             if (this.viewportDimensions) {
  716.                 elem.setAttribute("viewWidth",this.viewportDimensions.width);
  717.                 elem.setAttribute("viewHeight",this.viewportDimensions.height);
  718.             }
  719.         }
  720.         return elem;
  721.     }
  722. });        
  723.  
  724. DefaultTextExtractor.ExtractionTask = Prototype.Class.create({
  725.  
  726.     initialize: function(doc,url,options,callbacks) {
  727.         this.doc = doc;
  728.         this.url = url;
  729.         this.options = options || {};
  730.         this.callNumber = 0;
  731.         this.filtered = {};
  732.         this.callbacks = callbacks || {};
  733.         this.initParams();
  734.     },
  735.  
  736.     parseDocumentNode: function(block, node, offsetParentPosition, parentScrollPosition) {
  737.         if (this.isNodeStatusDone(node)) {
  738.             return true;
  739.         }
  740.         if (block.notInteresting || node === null || node.nodeType != Components.interfaces.nsIDOMNode.ELEMENT_NODE) {
  741.             this.markNodeStatusAsDone(node);
  742.             return true;
  743.         }
  744.         
  745.         var now = new Date();
  746.         if (now.getTime() - this.phaseStart.getTime() > this.taskPhaseMax) {
  747.             return false;
  748.         }
  749.  
  750.         var started = false;
  751.         if (this.isNodeStatusStarted(node)) {
  752.             started = true;
  753.         } else {
  754.             this.markNodeStatusAsStarted(node);
  755.         }
  756.     
  757.         var position = null;
  758.         var scrollPosition = null;
  759.         
  760.         var tag_type = DefaultTextExtractor.TAG_TYPES[node.nodeName.toLowerCase()];
  761.         
  762.         if (tag_type == "skip") {
  763.             this.markNodeStatusAsDone(node);
  764.             return true;
  765.         } else if (tag_type == "anchor") {
  766.             if (!started) {
  767.                 block.openLink();
  768.             }
  769.             if (!this.parseDocumentNodeChildren(block, node, offsetParentPosition, scrollPosition)) {
  770.                 return false;
  771.             }
  772.             block.closeLink();
  773.         } else if (tag_type == "ignore") {
  774.             if (!this.parseDocumentNodeChildren(block, node, offsetParentPosition, scrollPosition)) {
  775.                 return false;
  776.             }
  777.         } else if (tag_type == "par-start") {
  778.             if (!started) {
  779.                 block.openPar();
  780.             }
  781.             if (!this.parseDocumentNodeChildren(block, node, offsetParentPosition, scrollPosition)) {
  782.                 return false;
  783.             }
  784.         } else if (tag_type == "par") {
  785.             if (!started) {
  786.                 block.openPar();
  787.             }
  788.             if (!this.parseDocumentNodeChildren(block, node, offsetParentPosition, scrollPosition)) {
  789.                 return false;
  790.             }
  791.             block.closePar();
  792.         } else if (tag_type == "line-break") {
  793.             if (!started) {
  794.                 block.addLineBreak();
  795.             }
  796.             if (!this.parseDocumentNodeChildren(block, node, offsetParentPosition, scrollPosition)) {
  797.                 return false;
  798.             }
  799.         } else if (tag_type == "header") {
  800.             var level = node.nodeName[1];
  801.             if (!started) {
  802.                 block.openHeader(level);
  803.             }
  804.             if (!this.parseDocumentNodeChildren(block, node, offsetParentPosition, scrollPosition)) {
  805.                 return false;
  806.             }
  807.             block.closePar();
  808.         } else if (tag_type == "div") {
  809.             if (!started) {
  810.                 var nodeId = this.setNodeId(node);
  811.                 var newBlock = block.openBlock(node,nodeId,position,scrollPosition);
  812.                 block = newBlock;
  813.             } else {
  814.                 var nodeId = this.getNodeId(node);
  815.                 if (!nodeId) {
  816.                     nodeId = this.setNodeId(node);
  817.                 }
  818.                 block = block.getBlockByElement(nodeId);
  819.                 if (!block) {
  820.                     var newBlock = block.openBlock(node,nodeId,position,scrollPosition);
  821.                     block = newBlock;
  822.                 }
  823.             }
  824.             if (!this.parseDocumentNodeChildren(block, node, offsetParentPosition, scrollPosition)) {
  825.                 return false;
  826.             }
  827.         } else {
  828.             if (!this.parseDocumentNodeChildren(block, node, offsetParentPosition, scrollPosition)) {
  829.                 return false;
  830.             }
  831.         }
  832.         this.markNodeStatusAsDone(node);
  833.         return true;
  834.     },
  835.  
  836.     parseDocumentNodeChildren: function(block, node, basePositionForChildren, scrollPosition) {
  837.         var children = node.childNodes;
  838.         for (var ci = 0; ci < children.length; ++ci) {
  839.             var child = children[ci];
  840.             switch (child.nodeType) {
  841.                 case Components.interfaces.nsIDOMNode.ELEMENT_NODE:
  842.                     if (!this.parseDocumentNode(block,child,basePositionForChildren,scrollPosition)) {
  843.                         return false;
  844.                     }
  845.                     break;
  846.                 case Components.interfaces.nsIDOMNode.TEXT_NODE:
  847.                 case Components.interfaces.nsIDOMNode.CDATA_SECTION_NODE:
  848.                     if (!this.isNodeStatusDone(child)) {
  849.                         block.addText(node,child.nodeValue);
  850.                         this.markNodeStatusAsDone(child);
  851.                     }
  852.                     break;
  853.             }
  854.         }
  855.         return true;
  856.     },
  857.  
  858.     markNodeStatusAsDone: function(node) {
  859.         node.setUserData("glydoParsedNodeStatus","DONE",null);
  860.     },
  861.  
  862.     markNodeStatusAsStarted: function(node) {
  863.         node.setUserData("glydoParsedNodeStatus","STARTED",null);
  864.     },
  865.  
  866.     isNodeStatusDone: function(node) {
  867.         return node.getUserData("glydoParsedNodeStatus") == "DONE";
  868.     },
  869.     
  870.     isNodeStatusStarted: function(node) {
  871.         return node.getUserData("glydoParsedNodeStatus") == "STARTED";
  872.     },
  873.     
  874.     setNodeId: function(node) {
  875.         var id = Utils.uuid1();
  876.         node.setUserData("glydoUniqueId",id,null);
  877.         return id;
  878.     },
  879.  
  880.     getNodeId: function(node) {
  881.         return node.getUserData("glydoUniqueId");
  882.     },
  883.  
  884.     calcHeaderProbability: function(bestBlock, candidate, pageTitle) {
  885.         if (Prototype.S.strip(candidate.toText()).length == 0) {
  886.             return 0;
  887.         }
  888.         var score = 1;
  889.         var containingBlock = null;
  890.         // give higher score to h1 or h2
  891.         if (candidate instanceof DefaultTextExtractor.Header) {
  892.             score *= 4-candidate.level;
  893.             containingBlock = candidate.containingBlock;
  894.         } else {
  895.             // give higher score to larger fonts
  896.             var fontSize = Utils.getPixelsFromStyleSizeStr(candidate.style["fontSize"]);
  897.             score *= fontSize / 16.0; 
  898.             containingBlock = candidate.container.containingBlock;
  899.         }
  900.         if (containingBlock.notInteresting) {
  901.             return 0;
  902.         }
  903.         var text = Prototype.S.strip(candidate.toText());
  904.         // if the candidate is a part of the page title it gives it a serious boost...
  905.         if (pageTitle !== null && (pageTitle.indexOf(text) != -1) && text.length > 10) {
  906.             score *= 3;
  907.         }
  908.         if (containingBlock == bestBlock) {
  909.             score *= 2;
  910.         } else {
  911.             // not too close to the top
  912.             if (containingBlock.position.top < 50) {
  913.                 score /= 2;
  914.             }
  915.             // not too far down from the article (even though a little down is ok, cause there can be another
  916.             // block in between)
  917.             if (containingBlock.position.top - bestBlock.position.top > 100) {
  918.                 score /= 3;
  919.             }
  920.             // not too to the side from the article
  921.             var horizDiff = containingBlock.position.left - bestBlock.position.left;
  922.             if (horizDiff > 50 || horizDiff < -200) {
  923.                 score /= 3;
  924.             }
  925.         }
  926.         return score;
  927.     },
  928.     
  929.     extractHeader: function(block, bestBlock, pageTitle) {
  930.         var headerList = block.collectHeaderCandidates();
  931.         var maxHeaderScore = 0;
  932.         var header = null;
  933.         if (headerList !== null) { 
  934.             for (var hi = 0; hi < headerList.length; ++hi) {
  935.                 if (!(headerList[hi] instanceof DefaultTextExtractor.Header) &&
  936.                     !(headerList[hi] instanceof DefaultTextExtractor.Text)) {
  937.                     continue;
  938.                 }
  939.                 var headerScore = this.calcHeaderProbability(bestBlock, headerList[hi], pageTitle);
  940.                 if (headerScore > maxHeaderScore) {
  941.                     maxHeaderScore = headerScore;
  942.                     header = headerList[hi]; 
  943.                 }
  944.             }
  945.         }
  946.         
  947.         // If headline and page title match, we indicate a high certainty
  948.         if ((pageTitle !== null) && (header !== null) && 
  949.                 (pageTitle.indexOf(header.toText()) != -1)) {
  950.         }
  951.         return ({header: header, headerCertainty: maxHeaderScore}); 
  952.     },
  953.  
  954.     extractNormalParagraphs: function(block) {
  955.         
  956.         // Get list of non-header texts
  957.         paraList = block.getNormalParagraphs(this.paragraphDisqualifyingWords);
  958.         return paraList;
  959.     },
  960.     
  961.     extractNormalParagraphsAsXml: function(block,parent) {
  962.         var paraList = this.extractNormalParagraphs(block);
  963.         for (var ti = 0; ti < paraList.length; ++ti) {
  964.             var x = paraList[ti].toXml(parent.ownerDocument)
  965.             if (x !== null) {
  966.                 parent.appendChild(x);
  967.             }
  968.         }
  969.     },
  970.     
  971.     getContextItem: function(doc,rootBlock, bestBlock, pageTitle) {
  972.         var item = {
  973.                 "@type": "text",
  974.                 title: {
  975.                     "__content": ""
  976.                 },
  977.                 body: {
  978.                     "__content": ""
  979.                 },
  980.         };
  981.         var ht = this.extractHeader(rootBlock, bestBlock, pageTitle);
  982.         item.title["@certainty"] = ht.headerCertainty;
  983.         if (ht.header !== null) {
  984.             item.title["__content"] = ht.header.toText();
  985.         }
  986.  
  987.         var textDoc = doc.implementation.createDocument("","",null);
  988.         var body = textDoc.createDocumentFragment();
  989.         textDoc.appendChild(body);
  990.         this.extractNormalParagraphsAsXml(bestBlock,body);
  991.         item.body["@score"] = bestBlock.maxScore;
  992.         item.body["__content"] = body;
  993.  
  994.         return item;
  995.     },
  996.  
  997.     initParams: function() {
  998.         var defaults = ({
  999.             paragraphDisqualifyingWords:
  1000.                 [
  1001.                  "\u00A9",
  1002.                  "registered trademark",
  1003.                  "all rights reserved",
  1004.                  ["inappropriate", "comments"],
  1005.                  ["profanity", "comments"],
  1006.                  ["terms", "conditions"]
  1007.                 ],
  1008.             taskPhaseMax: Prefs.task_phase_max,
  1009.             totalTaskMax: Prefs.total_task_max,
  1010.             taskBreak: Prefs.task_break,
  1011.         });
  1012.         this.setParameter("paragraphDisqualifyingWords",this.options,defaults);
  1013.         this.setParameter("taskPhaseMax",this.options,defaults);
  1014.         this.setParameter("totalTaskMax",this.options,defaults);
  1015.         this.setParameter("taskBreak",this.options,defaults);
  1016.     },
  1017.  
  1018.     setParameter: function(name,params,defaults) {
  1019.         var p = params[name];
  1020.         if (p === undefined) {
  1021.             p = defaults[name];
  1022.         }
  1023.         this[name] = p;
  1024.     },
  1025.     
  1026.     execute: function() {
  1027.         this.callNumber++;
  1028.         if (!this.rootBlock) {
  1029.             this.taskStart = new Date();
  1030.             this.rootBlock = new DefaultTextExtractor.Block(0,this.doc.documentElement);
  1031.         }
  1032.         this.phaseStart = new Date();
  1033.         if (!this.parseDocumentNode(this.rootBlock, this.doc.documentElement)) {
  1034.             var after = new Date();
  1035.             if (after.getTime() - this.taskStart.getTime() > this.totalTaskMax) {
  1036.                 if (this.callbacks["notifyTaskFailed"]) {
  1037.                     this.callbacks["notifyTaskFailed"]("Text extraction took too much");
  1038.                 }
  1039.             } else {
  1040.                 this.doc.defaultView.setTimeout(Prototype.F.bind(this.execute, this), this.taskBreak);
  1041.             }
  1042.         } else {
  1043.             var after = new Date();
  1044.             
  1045.             var bestBlock = this.rootBlock.getBestBlock();
  1046.  
  1047.             var item = this.getContextItem(this.doc,this.rootBlock, bestBlock, this.doc.title);
  1048.             if (this.callbacks["notifyContextItemExtracted"]) {
  1049.                 this.callbacks["notifyContextItemExtracted"](item);
  1050.             }
  1051.             if (this.callbacks["notifyTaskDone"]) {
  1052.                 this.callbacks["notifyTaskDone"]();
  1053.             }
  1054.         }
  1055.     },
  1056.     
  1057. });
  1058.